/*
 *  OSXBGEPHY.cpp
 *  OSXBGE
 *
 *  Created by Stafford Brunk on 2/6/09.
 *  Copyright 2009 Northpole Software. All rights reserved.
 *
 */

#include "OSXBGE.h"
#include "BGE_REG.h"
#include "BGEHW_REG.h"

void OSXBGE::getPhyID()
{
	int id1 = bge_miibus_readreg(sc, PHY_ADDR, MII_PHYIDR1);
	int id2 = bge_miibus_readreg(sc, PHY_ADDR, MII_PHYIDR2);
	
	int hw_phy_id  = (id1 & 0xffff) << 10;
	hw_phy_id |= (id2 & 0xfc00) << 16;
	hw_phy_id |= (id2 & 0x03ff) <<  0;
	
	fPhyID = hw_phy_id & PHY_ID_MASK;
}

void OSXBGE::publishMedia()
{
	UInt16	status = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_BMSR);
	UInt16	exStatus = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_EXTSTS);
	
	_phyAddMediumType(kIOMediumEthernetAuto, 0, MEDIUM_TYPE_AUTO);
	
	if (status & BRGPHY_STATUS_10_HD)
		_phyAddMediumType(kIOMediumEthernet10BaseT | kIOMediumOptionHalfDuplex, 10 * MBPS, MEDIUM_TYPE_10_HD);
	
	if (status & BRGPHY_STATUS_10_FD)
		_phyAddMediumType(kIOMediumEthernet10BaseT | kIOMediumOptionFullDuplex, 10 * MBPS, MEDIUM_TYPE_10_FD);
	
	if (status & BRGPHY_STATUS_100_TX_HD)
		_phyAddMediumType(kIOMediumEthernet100BaseTX | kIOMediumOptionHalfDuplex, 100 * MBPS, MEDIUM_TYPE_100_TX_HD);
	
	if (status & BRGPHY_STATUS_100_TX_FD)
		_phyAddMediumType(kIOMediumEthernet100BaseTX | kIOMediumOptionFullDuplex, 100 * MBPS, MEDIUM_TYPE_100_TX_FD);
	
	if (status & BRGPHY_STATUS_100_T4)
		_phyAddMediumType(kIOMediumEthernet100BaseT4, 100 * MBPS, MEDIUM_TYPE_100_T4);
	
	if (exStatus & BRGPHY_EXTSTS_X_HD_CAP)
		_phyAddMediumType(kIOMediumEthernet1000BaseTX | kIOMediumOptionHalfDuplex, 1000 * MBPS, MEDIUM_TYPE_1000_TX_HD);
	
	if (exStatus & BRGPHY_EXTSTS_X_FD_CAP)
		_phyAddMediumType(kIOMediumEthernet1000BaseTX | kIOMediumOptionFullDuplex, 1000 * MBPS, MEDIUM_TYPE_1000_TX_FD);
}

bool OSXBGE::_phyAddMediumType(UInt32 type, UInt32 speed, UInt32 index)
{	
	IONetworkMedium	* medium;
	bool ret = false;
	
	medium = IONetworkMedium::medium(type, speed, 0, index);
	
	if (medium)
	{
		ret = IONetworkMedium::addMedium(fMediaDict, medium);
		
		if (ret)
			fMediaTable[index] = medium;
		
		medium->release();
	}
	
	return ret;
}

IONetworkMedium * OSXBGE::_phyGetMediumWithType(UInt32 type)
{
	DLOG("getMediumWithType %04x", type);
	
	if (type < MEDIUM_TYPE_INVALID)
		return fMediaTable[type];
	else
	{
		DLOG("No index");
		return 0;
	}
}

void OSXBGE::phyStatus()
{
	int bmcr, bmsr, aux;
	
	bmsr = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_BMSR) | bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_BMSR);
	aux = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_AUXSTS);
	
	/* Loopback is enabled. */
	if (bmcr & BRGPHY_BMCR_LOOP) {
		
		DLOG("Loopback");
	}
	
	/* Autoneg is still in progress. */
	if ((bmcr & BRGPHY_BMCR_AUTOEN) && (bmsr & BRGPHY_BMSR_ACOMP) == 0)
	{
		DLOG("Autoneg still trying");
		return;
	}	
	
	/* If copper link is up, get the negotiated speed/duplex. */
	if (aux & BRGPHY_AUXSTS_LINK) 
	{
		switch (aux & BRGPHY_AUXSTS_AN_RES) 
		{
			case BRGPHY_RES_1000FD:
				DLOG("1000 Full Duplex");
				break;
			case BRGPHY_RES_1000HD:
				DLOG("1000 Half Duplex");
				break;
			case BRGPHY_RES_100FD:
				DLOG("100 Full Duplex");
				break;
			case BRGPHY_RES_100T4:
				DLOG("100 T4");
				break;
			case BRGPHY_RES_100HD:
				DLOG("100 Half Duplex");
				break;
			case BRGPHY_RES_10FD:
				DLOG("10 Full Duplex");
				break;
			case BRGPHY_RES_10HD:
				DLOG("10 Half Duplex");
				break;
			default:
				DLOG("None");
				break;
		}
	}
}

bool OSXBGE::phySetMedium(mediumType_t medium)
{
	phyReset(sc);
	
	int speed = 0;
	
	switch (medium)
	{
		case MEDIUM_TYPE_10_HD:
		case MEDIUM_TYPE_10_FD:
			speed = BRGPHY_S10;
			break;
		case MEDIUM_TYPE_100_T4:
		case MEDIUM_TYPE_100_TX_HD:
		case MEDIUM_TYPE_100_TX_FD:
			speed = BRGPHY_S100;
			break;
		case MEDIUM_TYPE_1000_TX_HD:
		case MEDIUM_TYPE_1000_TX_FD:
			speed = BRGPHY_S1000;
			break;
		default:
			return false;
			break;
	}
	
	/* Disable 10/100 advertisements. */
	bge_miibus_writereg(sc, PHY_ADDR, BRGPHY_MII_ANAR, BRGPHY_SEL_TYPE);
	/* Write forced link speed. */
	bge_miibus_writereg(sc, PHY_ADDR, BRGPHY_MII_BMCR, speed);
	
	return true;
}

bool OSXBGE::phyReset(struct bge_softc *sc)
{
	if (phyBMCRReset(sc) != 0)
		return false;
	
	//Post reset stuff
	if(sc->bge_asicrev == BGE_ASICREV_BCM5906)
		bge_miibus_writereg(sc, PHY_ADDR, BRGPHY_MII_EPHY_PTEST, 0x12);
	
	return true;
}

#define PHY_NWAY_TIMEOUT				5000	// ms
#define PHY_NWAY_DELAY					20		// ms

bool OSXBGE::phyWaitForAutoNegotiation()
{
	int i = PHY_NWAY_TIMEOUT;
	UInt16	status;
	
	while (i > 0) 
	{
		status = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_BMSR);
		if (!status)
			return false;
		
		if (status & BRGPHY_BMSR_ACOMP)
			return true;
		
		IOSleep(PHY_NWAY_DELAY);
		i -= PHY_NWAY_DELAY;
	}
	return false;
}

void OSXBGE::phyReportLinkStatus( bool firstPoll )
{
    UInt16	status;
    UInt16  statusChange;
	
    // Read PHY status register.
    status = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_BMSR);
	
    // Detect a change in the two link related bits.
    // Remember that the link status bit will latch a link fail
    // condition (should not miss a link down event).
	
    statusChange = ( fPhyPrevStatus ^ status ) & ( BRGPHY_BMSR_LINK | BRGPHY_BMSR_ACOMP );
	
    if ( statusChange || firstPoll )
    {
        if ( firstPoll )
        {
            // For the initial link status poll, wait a bit, then
            // re-read the status register to clear any latched bits.
			
            phyWaitForAutoNegotiation();
			
            status = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_BMSR);
            status = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_BMSR);
        }
		
        // Determine link status
        if ( status & BRGPHY_BMSR_LINK )
        {
            // Link is up
			DLOG("Link is up");
            IONetworkMedium * activeMedium;
            activeMedium = _phyGetMediumWithType( phyGetActiveMedium() );
			
            setLinkStatus( kIONetworkLinkValid | kIONetworkLinkActive, activeMedium);
        }
        else
        {
            // Link is down
			DLOG("Link is down");
            setLinkStatus( kIONetworkLinkValid, 0 );
        }
		
        // Save status		
        fPhyPrevStatus = status;
    }
}

mediumType_t OSXBGE::phyGetActiveMedium()
{
    mediumType_t  medium;
	
    int anar = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_ANAR);
	int anlpar = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_ANLPAR);
    
	int common = anar & anlpar;
    
    //No gigabit support right now
	
    if ( common & BRGPHY_ANAR_TX_FD )
	{
		DLOG("100 TX FD Active");
		medium = MEDIUM_TYPE_100_TX_FD;
	}
	
    else if ( common & BRGPHY_ANAR_T4 )
	{
		DLOG("100 T4 Active");
		medium = MEDIUM_TYPE_100_T4;
	}
	
    else if ( common & BRGPHY_ANAR_TX_HD )
	{
		DLOG("100 TX HD Active");
		medium = MEDIUM_TYPE_100_TX_HD;
	}
	
    else if ( common & BRGPHY_ANAR_10_FD )
	{
		DLOG("10 TX FD Active");
		medium = MEDIUM_TYPE_10_FD;
	}
	
    else
	{
		DLOG("10 TX HD Active");
		medium = MEDIUM_TYPE_10_HD;
	}
	
    return medium;
}

int OSXBGE::phyBMCRReset(struct bge_softc *sc)
{
	UInt32 phy_control;
	int limit, err;
	
	phy_control = BRGPHY_BMCR_RESET;
	bge_miibus_writereg(sc, PHY_ADDR, BRGPHY_MII_BMCR, phy_control);
	
	limit = 5000;
	while (limit--)
	{
		err = bge_miibus_readreg(sc, PHY_ADDR, BRGPHY_MII_BMCR);
		
		if (err != 0)
			return -1;
		
		if ((phy_control & BRGPHY_BMCR_RESET) == 0)
		{
			IODelay(40);
			break;
		}
		
		IODelay(40);
	}
	
	if (limit <= 0)
		return -1;
	
	return 0;
}